﻿using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Media.Media3D;
using LFSRecord2.Misc;
using LFSRecord2.Model.Undo;
using LFSRecord2.Structures;
using System.Collections.ObjectModel;

namespace LFSRecord2.Model
{
    /// <summary>
    /// Property class for animated Point3D values
    /// </summary>
    public class CompLayerPropertyPoint3D : CompLayerPropertyBase
    {
        public Point3D DefaultValue { get; private set; }
        public bool HasAnimationPath { get; private set; }

        private Collection<Point3DFrameCacheObject> _point3DFrameCache = new Collection<Point3DFrameCacheObject>();
        public Collection<Point3DFrameCacheObject> Point3DFrameCache
        {
            get { return (Collection<Point3DFrameCacheObject>)_point3DFrameCache; }
            private set
            {
                _point3DFrameCache = value;
                OnPropertyChanged("Point3DFrameCache");
            }
        }

        private double _currentXValue = 0;
        public double CurrentXValue
        {
            get { return (double)_currentXValue; }
            set 
            {
                double oldValue = _currentXValue;
                _currentXValue = value;

                if (PropertyName == "Orientation")
                {
                    if (_currentXValue < 0)
                        _currentXValue += 360;
                    else if (_currentXValue >= 360)
                        _currentXValue -= 360;
                }

                OnPropertyChanged("CurrentXValue");
                if (ValueLock)
                {
                    _currentYValue += _currentXValue - oldValue;
                    _currentZValue += _currentXValue - oldValue;
                    OnPropertyChanged("CurrentYValue");
                    OnPropertyChanged("CurrentZValue");
                }
            }
        }
        private double _currentYValue = 0;
        public double CurrentYValue
        {
            get { return (double)_currentYValue; }
            set
            {
                double oldValue = _currentYValue;
                _currentYValue = value;

                if (PropertyName == "Orientation")
                {
                    if (_currentYValue < 0)
                        _currentYValue += 360;
                    else if (_currentYValue >= 360)
                        _currentYValue -= 360;
                }

                OnPropertyChanged("CurrentYValue");
                if (ValueLock)
                {
                    _currentXValue += _currentYValue - oldValue;
                    _currentZValue += _currentYValue - oldValue;
                    OnPropertyChanged("CurrentXValue");
                    OnPropertyChanged("CurrentZValue");
                }
            }
        }
        private double _currentZValue = 0;
        public double CurrentZValue
        {
            get { return (double)_currentZValue; }
            set
            {
                double oldValue = _currentZValue;
                _currentZValue = value;

                if (PropertyName == "Orientation")
                {
                    if (_currentZValue < 0)
                        _currentZValue += 360;
                    else if (_currentZValue >= 360)
                        _currentZValue -= 360;
                }

                OnPropertyChanged("CurrentZValue");
                if (ValueLock)
                {
                    _currentYValue += _currentZValue - oldValue;
                    _currentXValue += _currentZValue - oldValue;
                    OnPropertyChanged("CurrentXValue");
                    OnPropertyChanged("CurrentYValue");
                }
            }
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            clearPoint3DFrameCache();
        }

        public CompLayerPropertyPoint3D(CompLayerBase parentLayer, int propertyIndex, string propertyName, Point3D defaultValue, LayerPropertyValueMeaning propertyValueMeaning)
            : this(parentLayer, propertyIndex, propertyName, defaultValue, propertyValueMeaning, false)
        {
        }
        public CompLayerPropertyPoint3D(CompLayerBase parentLayer, int propertyIndex, string propertyName, Point3D defaultValue, LayerPropertyValueMeaning propertyValueMeaning, bool hasAnimationPath)
        {
            //_keyFrames = new List<CustomKeyFrame>();
            //KeyFrames = new SortableObservableCollection<CustomKeyFrame>(_keyFrames);

            IsEditable = true;
            ParentLayer = parentLayer;
            PropertyIndex = propertyIndex;
            PropertyName = propertyName;
            PropertyValueType = LayerPropertyValueTypes.Point3D;
            PropertyValueMeaning = propertyValueMeaning;
            HasAnimationPath = hasAnimationPath;
            DefaultValue = defaultValue;
            _currentXValue = DefaultValue.X;
            _currentYValue = DefaultValue.Y;
            _currentZValue = DefaultValue.Z;
        }

        public override void HandleNewPropertyBinding(CompLayerPropertyBase prop)
        {
            CompLayerPropertyPoint3D propPoint3D = (CompLayerPropertyPoint3D)prop;
            CurrentXValue = propPoint3D.CurrentXValue;
            CurrentYValue = propPoint3D.CurrentYValue;
            CurrentZValue = propPoint3D.CurrentZValue;

            ParentLayer.UpdatedCurrentValues = CompLayerPropertyUpdateTypes.Position | CompLayerPropertyUpdateTypes.Rotation;
            ParentLayer.DoneUpdatingCurrentValues = !ParentLayer.DoneUpdatingCurrentValues;

            RemoveCurrentValueListener();
            prop.PropertyChanged += new PropertyChangedEventHandler(prop_PropertyChanged);
        }

        void prop_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "CurrentXValue")
                CurrentXValue = ((CompLayerPropertyPoint3D)sender).CurrentXValue;
            
            if (e.PropertyName == "CurrentYValue")
                CurrentYValue = ((CompLayerPropertyPoint3D)sender).CurrentYValue;
            
            if (e.PropertyName == "CurrentZValue")
                CurrentZValue = ((CompLayerPropertyPoint3D)sender).CurrentZValue;

            if (e.PropertyName == "CurrentXValue" ||
                e.PropertyName == "CurrentYValue" ||
                e.PropertyName == "CurrentZValue")
            {
                if (((CompLayerPropertyPoint3D)sender).PropertyName == "Look At")
                    ParentLayer.UpdatedCurrentValues = CompLayerPropertyUpdateTypes.Rotation;
                else if (((CompLayerPropertyPoint3D)sender).PropertyName == "Orientation")
                    ParentLayer.UpdatedCurrentValues = CompLayerPropertyUpdateTypes.Rotation;
                else if (((CompLayerPropertyPoint3D)sender).PropertyName == "Scale")
                    ParentLayer.UpdatedCurrentValues = CompLayerPropertyUpdateTypes.Scale;
                else
                    ParentLayer.UpdatedCurrentValues = CompLayerPropertyUpdateTypes.Position;

                //Debug.WriteLine("setting matrix");
                ParentLayer.SetCurrentTransformMatrix();
                ParentLayer.DoneUpdatingCurrentValues = !ParentLayer.DoneUpdatingCurrentValues;
            }
        }

        public override void RemoveCurrentValueListener()
        {
            if (BoundProperty != null)
                BoundProperty.PropertyChanged -= new PropertyChangedEventHandler(prop_PropertyChanged);
        }

        /// <summary>
        /// Creates, unless one already exists and returns a key at specified frame index.
        /// </summary>
        /// <param name="frame">The frame index at which to set the key</param>
        /// <returns></returns>
        public override CustomKeyFrame SetKeyAtCurrentTime(int frame)
        {
            //Debug.WriteLine("setKeyAtCurrentTime : " + PropertyName + "(" + frame + ")");
            if (frame < 0)
                return null;

            // Check if there isn't a key already
            CustomKeyFrame kf = GetKeyAtTime(frame);
            if (kf != null)
                return kf;

            //kf = SetKeyFrame(frame, (Point3D)GetValueAtTime(frame));
            kf = SetKeyFrame(frame, new Point3D(_currentXValue, _currentYValue, _currentZValue));
            if (!IsAnimated)
                IsAnimated = true;
            
            return kf;
        }

        public CustomPoint3DKeyFrame SetKeyFrame(int frame, Point3D newValue)
        {
            CustomPoint3DKeyFrame kf = new CustomPoint3DKeyFrame(frame, newValue);
            AddKeyFrame(kf);
            return kf;
        }

        public override void AddKeyFrame(CustomKeyFrame key)
        {
            AddKeyFrame(key, true);
        }
        public override void AddKeyFrame(CustomKeyFrame key, bool autoBezier)
        {
            CustomPoint3DKeyFrame newKey = (CustomPoint3DKeyFrame)key;
            CustomPoint3DKeyFrame kfpPrev, kfpNext;

            newKey.ParentProperty = this;
            newKey.ParentLayer = ParentLayer;

            // Insert or append the new key to the array
            int index = 0;
            bool haveInserted = false;
            foreach (CustomKeyFrame kf in KeyFrames)
            {
                if (kf.FrameNum > newKey.FrameNum)
                {
                    //Debug.WriteLine("Inserting Key at index " + index);
                    KeyFrames.Insert(index, newKey);
                    haveInserted = true;
                    break;
                }
                index++;
            }

            if (!haveInserted)
            {
                //Debug.WriteLine("Appending Key at end - " + index);
                KeyFrames.Add(newKey);
            }

            RewriteKeyFrameIndices();
            SetHasPreFrames();
            SetHasPostFrames();
            SetHasCurrentFrame();

            // Add event listeners
            newKey.PropertyChanged += KeyFramePropertyChangedHandler;
            
            //Debug.WriteLine("AutBezier was " + autoBezier);

            // Check if we need to set bezier properties to make line smooth
            if (autoBezier)
            {
                // First or last?
                if (newKey.KeyFrameIndex == 0 || newKey.KeyFrameIndex == KeyFrames.Count - 1)
                {
                    AutoBezierCore(newKey);
                }

                // Middle keyframe
                else
                {
                    kfpPrev = (CustomPoint3DKeyFrame)KeyFrames[newKey.KeyFrameIndex - 1];
                    kfpNext = (CustomPoint3DKeyFrame)KeyFrames[newKey.KeyFrameIndex + 1];
                    CustomPoint3DInterpolation.writeHandlesForInsertedBezierPoint(newKey, kfpPrev, kfpNext, Point3DFrameCache, KeyFrames[0].FrameNum);
                }
            }

            populatePoint3DCache();

            if (newKey.IsSelected)
            {
                HasSelectedKeyFrames = true;
                IsSelected = true;
                ParentLayer.IsSelected = ParentLayer.HasSelectedProperties();
                if (ParentLayer.IsCollapsed)
                    ParentLayer.IsCollapsed = false;
            }
            if (!_isAnimated)
            {
                _isAnimated = true;
                OnPropertyChanged("IsAnimated");
            }

            IsEdited = true;
        }

        public override void DeleteAllKeyframes()
        {
            KeyFrames.Clear();
            clearPoint3DFrameCache();
            IsEdited = true;
        }

        public override object GetValueAtFrame(int frame)
        {
            // If we have a binding (overrides everything)
            if (HasBoundProperty)
            {
                return BoundProperty.GetValueAtFrame(frame);
            }

            // There are no key frames
            if (!IsAnimated || KeyFrames.Count == 0)
                return new Point3D(CurrentXValue, CurrentYValue, CurrentZValue);

            // Time is before first key frame
            if (frame < KeyFrames[0].FrameNum)
                return ((CustomPoint3DKeyFrame)KeyFrames[0]).Value;

            // Time is beyond last key frame (we hold last value)
            if (frame >= KeyFrames[KeyFrames.Count - 1].FrameNum)
                return ((CustomPoint3DKeyFrame)KeyFrames[KeyFrames.Count - 1]).Value;

            if (HasAnimationPath)
            {
                // We will get a cached frame at given time
                return Point3DFrameCache[frame - KeyFrames[0].FrameNum].Value;
            }
            else
            {
                // We interpolate linearly in real time
                CustomPoint3DKeyFrame fromKey = new CustomPoint3DKeyFrame(0, ((CustomPoint3DKeyFrame)KeyFrames[0]).Value);
                CustomPoint3DKeyFrame toKey = null;

                foreach (CustomPoint3DKeyFrame kf in KeyFrames)
                {
                    if (frame < kf.FrameNum)
                    {
                        toKey = kf;
                        if (fromKey == null)
                            fromKey = kf;
                        break;
                    }
                    fromKey = kf;
                }

                if ((fromKey.Options & CustomKeyFrameOptions.Hold) > 0)
                    return fromKey.Value;

                frame -= fromKey.FrameNum;
                int numFrames = toKey.FrameNum - fromKey.FrameNum;

                double normalisedTime = CustomDoubleEase.EasingCore((double)frame / (double)numFrames, fromKey.EaseOutValue, toKey.EaseInValue);

                // Orientation 'shortest route' interpolation
                if (PropertyName == "Orientation")
                {
                    // Which way is the shortest path?
                    return new Point3D(
                        CustomDoubleInterpolation.InterpolateLinearOrientationShortestRoute(fromKey.Value.X, toKey.Value.X, normalisedTime),
                        CustomDoubleInterpolation.InterpolateLinearOrientationShortestRoute(fromKey.Value.Y, toKey.Value.Y, normalisedTime),
                        CustomDoubleInterpolation.InterpolateLinearOrientationShortestRoute(fromKey.Value.Z, toKey.Value.Z, normalisedTime));
                }
                else
                {
                    return CustomPoint3DInterpolation.InterpolateLinear(fromKey.Value, toKey.Value, normalisedTime);
                }
            }
        }

        /// <summary>
        /// Should be called after changing this property's currentvalues, so that either a new keyframe can be deleted, or an existing one adjusted
        /// </summary>
        /// <returns>A CustomKeyFrame in the case of a newly created one. Otherwise returns null.</returns>
        public override CustomKeyFrame OnCurrentValueChange()
        {
            if (!IsAnimated)
            {
                IsEdited = true;
                return null;
            }

            // Insert or adjust keyframe (according to CurrentValues)
            CustomPoint3DKeyFrame kf = (CustomPoint3DKeyFrame)GetKeyAtTime(LfsRecordController.FrameIndex);
            if (kf == null)
            {
                kf = (CustomPoint3DKeyFrame)SetKeyAtCurrentTime(LfsRecordController.FrameIndex);
                // No need to call populatePoint3DCache; it's already done when setting a keyframe
                return kf;
            }
            else
            {
                kf.Value = new Point3D(CurrentXValue, CurrentYValue, CurrentZValue);
                AutoBezierCore(kf);
                populatePoint3DCache();
                return null;
            }
        }

        public override bool SetCurrentValue(int frame)
        {
            //Debug.WriteLine("SetCurrentValue");
            Point3D p = (Point3D)GetValueAtFrame(frame);
            if (CurrentXValue == p.X && CurrentYValue == p.Y && CurrentZValue == p.Z)
                return false;
            
            CurrentXValue = p.X;
            CurrentYValue = p.Y;
            CurrentZValue = p.Z;
            
            return true;
        }

        public void populatePoint3DCache()
        {
            // Is our 3-double value not a path?
            if (!HasAnimationPath)
            {
                return;
            }

            //Debug.WriteLine("Populating Point3DCache");
            if (ActualDuration() == 0)
            {
                Point3DFrameCache = new Collection<Point3DFrameCacheObject>();
                if (PropertyName == "Position" || PropertyName == "Look At")
                    ParentLayer.SetCurrentTransformMatrix();
                return;
            }
            Collection<Point3DFrameCacheObject> pointCache = new Collection<Point3DFrameCacheObject>();

            Point3DFrameCacheObject[] section;
            CustomPoint3DKeyFrame fromKey = null;
            int numFrames;
            foreach (CustomPoint3DKeyFrame kf in KeyFrames)
            {
                if (fromKey == null || fromKey.FrameNum == kf.FrameNum)
                {
                    fromKey = kf;
                    continue;
                }

                if (fromKey.FrameNum + 1 == kf.FrameNum)
                {
                    pointCache.Add(new Point3DFrameCacheObject() { Value = fromKey.Value, BezierTime = MathHelper.PointDistance(fromKey.Value, kf.Value) });
                    fromKey = kf;
                    continue;
                }

                // Which interpolation?
                if ((fromKey.Options & CustomKeyFrameOptions.Hold) > 0)
                {
                    numFrames = kf.FrameNum - fromKey.FrameNum;
                    section = new Point3DFrameCacheObject[numFrames];
                    for (int b = 0; b < numFrames; b++)
                    {
                        section[b] = new Point3DFrameCacheObject() { Value = fromKey.Value };
                    }
                }
                else if (fromKey.InterpolationTypeOut == KeyFrameInterpolationTypes.Bezier || kf.InterpolationTypeIn == KeyFrameInterpolationTypes.Bezier)
                {
                    section = CustomPoint3DInterpolation.createBezierSection(fromKey, kf);
                }
                else
                {
                    // Write distance value for previous keyframe
                    fromKey.DistanceToNextKeyFrame = MathHelper.PointDistance(fromKey.Value, kf.Value);

                    section = CustomPoint3DInterpolation.createLinearSection(fromKey, kf);
                }
                fromKey = kf;

                for (int b = 0; b < section.Length; b++)
                {
                    pointCache.Add(section[b]);
                }
            }

            // Add final keyframe to the cache
            Point3DFrameCacheObject finalObject;
            finalObject = new Point3DFrameCacheObject();
            finalObject.Value = fromKey.Value;
            finalObject.BezierTime = 0;
            pointCache.Add(finalObject);

            Point3DFrameCache = pointCache;

            if (PropertyName == "Position" || PropertyName == "Look At")
                ParentLayer.SetCurrentTransformMatrix();

            //Debug.WriteLine("Total frames in Point3DFrameCache : " + Point3DFrameCache.Count);
        }

        public void clearPoint3DFrameCache()
        {
            Point3DFrameCache = new Collection<Point3DFrameCacheObject>();
        }

        public int ActualDuration()
        {
            return (KeyFrames.Count == 0) ? 0 : KeyFrames[KeyFrames.Count - 1].FrameNum + 1;
        }

        public void AutoBezierWholePath()
        {
            foreach (CustomPoint3DKeyFrame kf in KeyFrames)
            {
                AutoBezierCore(kf);
            }
        }

        public void AutoBezierCore(CustomPoint3DKeyFrame kf)
        {
            CustomPoint3DKeyFrame kf2, kf3;

            if (KeyFrames.Count == 1)
                return;

            // See at which location this key frame is (outer edge or somewhere in the middle)
            if (kf.KeyFrameIndex == 0 || kf.KeyFrameIndex == KeyFrames.Count - 1)
            {
                // Start frame
                if (kf.KeyFrameIndex == 0)
                {
                    // Set the adjacent frame variable
                    kf2 = (CustomPoint3DKeyFrame)KeyFrames[1];

                    // Adjust this frame's bezier handles
                    if ((kf.Options & CustomKeyFrameOptions.AutoBezier) > 0)
                    {
                        // Get the vector between this and next
                        AutoBezierCoreSetControls(kf, new Vector3D(kf.Value.X - kf2.Value.X, kf.Value.Y - kf2.Value.Y, kf.Value.Z - kf2.Value.Z));
                    }

                    // Adjust adjacent frame's bezier handles
                    if ((kf2.Options & CustomKeyFrameOptions.AutoBezier) > 0)
                    {
                        // Is the adjacent frame the last frame?
                        if (kf2.KeyFrameIndex == KeyFrames.Count - 1)
                        {
                            // kf2 is last frame
                            AutoBezierCoreSetControls(kf2, new Vector3D(kf.Value.X - kf2.Value.X, kf.Value.Y - kf2.Value.Y, kf.Value.Z - kf2.Value.Z));
                        }
                        else
                        {
                            // kf2 has more frames after it
                            kf3 = (CustomPoint3DKeyFrame)KeyFrames[kf2.KeyFrameIndex + 1];
                            AutoBezierCoreSetControls(kf2, new Vector3D(kf.Value.X - kf3.Value.X, kf.Value.Y - kf3.Value.Y, kf.Value.Z - kf3.Value.Z));
                        }
                    }
                }

                // Last frame
                else
                {
                    // Set the adjacent frame variable
                    kf2 = (CustomPoint3DKeyFrame)KeyFrames[kf.KeyFrameIndex - 1];

                    // Adjust this frame's bezier handles
                    if ((kf.Options & CustomKeyFrameOptions.AutoBezier) > 0)
                    {
                        // Get the vector between this and previous
                        AutoBezierCoreSetControls(kf, new Vector3D(kf2.Value.X - kf.Value.X, kf2.Value.Y - kf.Value.Y, kf2.Value.Z - kf.Value.Z));
                    }

                    // Adjust adjacent frame's bezier handles
                    if ((kf2.Options & CustomKeyFrameOptions.AutoBezier) > 0)
                    {
                        // Is the adjacent frame the first frame?
                        if (kf2.KeyFrameIndex == 0)
                        {
                            // kf2 is first frame
                            AutoBezierCoreSetControls(kf2, new Vector3D(kf2.Value.X - kf.Value.X, kf2.Value.Y - kf.Value.Y, kf2.Value.Z - kf.Value.Z));
                        }
                        else
                        {
                            // kf2 has more frames before it
                            kf3 = (CustomPoint3DKeyFrame)KeyFrames[kf2.KeyFrameIndex - 1];
                            AutoBezierCoreSetControls(kf2, new Vector3D(kf3.Value.X - kf.Value.X, kf3.Value.Y - kf.Value.Y, kf3.Value.Z - kf.Value.Z));
                        }
                    }
                }
            }
            else
            {
                // Middle

                // Adjust Previous key frame
                kf2 = (CustomPoint3DKeyFrame)KeyFrames[kf.KeyFrameIndex - 1];
                if ((kf2.Options & CustomKeyFrameOptions.AutoBezier) > 0)
                {
                    if (kf2.KeyFrameIndex == 0)
                    {
                        // kf2 is the first keyframe in the animation
                        AutoBezierCoreSetControls(kf2, new Vector3D(kf2.Value.X - kf.Value.X, kf2.Value.Y - kf.Value.Y, kf2.Value.Z - kf.Value.Z));
                    }
                    else
                    {
                        // Grab the one before kf2 to calculate with
                        kf3 = (CustomPoint3DKeyFrame)KeyFrames[kf2.KeyFrameIndex - 1];
                        AutoBezierCoreSetControls(kf2, new Vector3D(kf3.Value.X - kf.Value.X, kf3.Value.Y - kf.Value.Y, kf3.Value.Z - kf.Value.Z));
                    }
                }

                // Adjust Next key frame
                kf2 = (CustomPoint3DKeyFrame)KeyFrames[kf.KeyFrameIndex + 1];
                if ((kf2.Options & CustomKeyFrameOptions.AutoBezier) > 0)
                {
                    if (kf2.KeyFrameIndex == KeyFrames.Count - 1)
                    {
                        // kf2 is the last keyframe in the animation
                        AutoBezierCoreSetControls(kf2, new Vector3D(kf.Value.X - kf2.Value.X, kf.Value.Y - kf2.Value.Y, kf.Value.Z - kf2.Value.Z));
                    }
                    else
                    {
                        // Grab the one after kf2 to calculate with
                        kf3 = (CustomPoint3DKeyFrame)KeyFrames[kf2.KeyFrameIndex + 1];
                        AutoBezierCoreSetControls(kf2, new Vector3D(kf.Value.X - kf3.Value.X, kf.Value.Y - kf3.Value.Y, kf.Value.Z - kf3.Value.Z));
                    }
                }
            }
        }

        double _autoBezierDefaultControlLength = 1.0d / 6.0d;
        void AutoBezierCoreSetControls(CustomPoint3DKeyFrame kf, Vector3D vec1)
        {
            kf.BezierControlOut = new Point3D(
                vec1.X * -_autoBezierDefaultControlLength,
                vec1.Y * -_autoBezierDefaultControlLength,
                vec1.Z * -_autoBezierDefaultControlLength
                );
            kf.BezierControlIn = new Point3D(
                vec1.X * _autoBezierDefaultControlLength,
                vec1.Y * _autoBezierDefaultControlLength,
                vec1.Z * _autoBezierDefaultControlLength
                );
        }

        public void KeyFramePropertyChangedHandler(object sender, PropertyChangedEventArgs e)
        {
            CustomPoint3DKeyFrame kfp, kfp2;

            if (e.PropertyName == "Value" || e.PropertyName == "FrameNum")
            {
                IsEdited = true;

                if (!HasAnimationPath)
                {
                    // Set DistanceToNextKeyFrame (difference between this value and next)
                    kfp = (CustomPoint3DKeyFrame)sender;
                    if (kfp.KeyFrameIndex < KeyFrames.Count - 1)
                    {
                        kfp2 = (CustomPoint3DKeyFrame)KeyFrames[kfp.KeyFrameIndex + 1];
                        kfp.DistanceToNextKeyFrame = MathHelper.PointDistance(kfp2.Value, kfp.Value);
                    }
                    else
                    {
                        kfp.DistanceToNextKeyFrame = 0;
                    }

                    // Set DistanceToNextKeyFrame of previous keyframe
                    if (kfp.KeyFrameIndex > 0)
                    {
                        kfp2 = (CustomPoint3DKeyFrame)KeyFrames[kfp.KeyFrameIndex - 1];
                        kfp2.DistanceToNextKeyFrame = MathHelper.PointDistance(kfp.Value, kfp2.Value);
                    }
                }
            }
            else if (e.PropertyName == "IsAnimated")
            {
                IsEdited = true;
                Debug.WriteLine("IS ANIMATED ON KEYFRAME HANDLER?!?!?!");
            }
            else if (e.PropertyName == "BezierControl")
            {
                populatePoint3DCache();
                IsEdited = true;
            }
            else if (e.PropertyName == "EaseInValue" ||
                     e.PropertyName == "EaseOutValue" ||
                     e.PropertyName == "HasHoldValue")
            {
                populatePoint3DCache();
                IsEdited = true;
            }
            else if (e.PropertyName == "InterpolationTypeIn" ||
                     e.PropertyName == "InterpolationTypeOut" ||
                     e.PropertyName == "Options")
            {
                kfp = (CustomPoint3DKeyFrame)sender;
                AutoBezierCore(kfp);
                if ((kfp.Options & CustomKeyFrameOptions.AutoBezier) > 0)
                {
                    // Has a previous key?
                    if (kfp.KeyFrameIndex > 0)
                        AutoBezierCore((CustomPoint3DKeyFrame)KeyFrames[kfp.KeyFrameIndex - 1]);
                    // Has a next key otherwise?
                    else if (kfp.KeyFrameIndex < KeyFrames.Count - 1)
                        AutoBezierCore((CustomPoint3DKeyFrame)KeyFrames[kfp.KeyFrameIndex + 1]);
                }
                populatePoint3DCache();
                IsEdited = true;
            }
            else if (e.PropertyName == "ReCacheDummy")
            {
                populatePoint3DCache();
            }
            else if (e.PropertyName == "ReCacheDummyWithAutoBezierCore")
            {
                AutoBezierCore((CustomPoint3DKeyFrame)sender);
                populatePoint3DCache();
                IsEdited = true;
            }
            else if (e.PropertyName == "IsSelected")
            {
                if (((CustomKeyFrame)sender).IsSelected)
                    HasSelectedKeyFrames = true;

                IsSelected = HasSelectedKeyFrames;
                ParentLayer.IsSelected = ParentLayer.HasSelectedProperties();
            }
        }

        public override PropertyDataBase DataStructure()
        {
            Debug.WriteLine("Creating Point3D Property Datastructure");

            PropertyPoint3DData propertyData = new PropertyPoint3DData()
            {
                ParentLayerIndex = ParentLayer.LayerIndex,
                PropertyIndex = PropertyIndex,
                IsSelected = IsSelected,
                IsAnimated = IsAnimated,
                ValueLock = ValueLock,
                BindingPropertyIndex = BindingPropertyIndex,
                BoundPropertyIgnoreParent = BoundPropertyIgnoreParent,
                NumKeyFrames = KeyFrames.Count,
                KeyFrames = new KeyFrameDataBase[KeyFrames.Count],

                HasAnimationPath = HasAnimationPath,
                CurrentXValue = _currentXValue,
                CurrentYValue = _currentYValue,
                CurrentZValue = _currentZValue
            };

            // Populate keyframes
            for (int a = 0; a < KeyFrames.Count; a++)
            {
                propertyData.KeyFrames[a] = KeyFrames[a].DataStructure();
            }

            return propertyData;
        }
    }
}
